import CodoBase from "../../../lib/CodoBase";
import { Wechat as wechat } from "../wechat";
import CodoError from "../../../lib/CodoError";

const ctxAccessTokenSymbol = Symbol("#codoWechatMPCacheAccessToken");
/**
 * 微信公众号 token管理
 */
export default class CodoWechatGzhAccessToken extends CodoBase {
	/**
	 * - 从微信公众平台获取access token
	 * - 2h内有效，可缓存起来
	 * - 注意expires_in 已按毫秒单位
	 * - https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
	 */
	async getAccessTokenFromWechat(appId: string, secret: string) {
		const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${secret}`;
		const res = await this.app.curl<wechat.mp.response.AccessToken | wechat.ICommonRespnose>(url, {
			method: "GET",
			dataType: "json",
			contentType: "json",
		});
		const { data } = res;
		if ("errcode" in data) {
			throw new CodoError(`请求微信公众平台获取公众号accessToken出错 %s %s`, data.errcode, data.errmsg);
		}
		const a = {
			appId,
			...data,
			update_time: Date.now(),
		} as wechat.mp.ILocalCacheAccessTokenObject;
		// 过期设置为毫秒
		a.expires_in = a.expires_in * 1000;
		return a;
	}

	/** 刷新应用的token，缓存在本地服务器的 */
	async refreshAccessToken(appId: string, secret: string) {
		const info = await this.getAccessTokenFromWechat(appId, secret);
		const key = getKey(info.appId);
		await this.dataManager.save(key, info);

		return info;
	}

	/**
	 *  从本地app中获取缓存好的token，用于业务逻辑，如果没有，将会获取一次新token
	 * - 如果有ctx，将会在ctx上挂载一份token，	用于一个ctx上下文内使用
	 */
	async getAccessToken(appId: string, secret: string) {
		const get = async () => {
			const key = getKey(appId);
			const tokenObj = await this.dataManager.get<wechat.mp.ILocalCacheAccessTokenObject>(key);

			if (!tokenObj) {
				const t = await this.refreshAccessToken(appId, secret);
				return t.access_token;
			} else if (Date.now() - tokenObj.update_time > tokenObj.expires_in - 100 * 1000) {
				// 如果现在与更新时差值，大于token有效期了，就更新token
				const newToken = await this.refreshAccessToken(appId, secret);
				return newToken.access_token;
			} else {
				return tokenObj.access_token;
			}
		};
		const t: string = await get();
		// 在ctx中挂载token，外部可使用
		if (this.app.currentContext) {
			this.app.currentContext[ctxAccessTokenSymbol as any] = t;
		}

		return t;
	}

	/** 尝试从ctx中获取token，如果没有，抛出异常 */
	getCurrentContextCacheAccessToken(): string {
		if (this.app.currentContext && this.app.currentContext[ctxAccessTokenSymbol as any]) {
			return this.app.currentContext[ctxAccessTokenSymbol as any];
		}
		throw new CodoError(`get access token error from app.currentContext`);
	}
}

function getKey(appid: string) {
	return `platform:wechat:gzh:accessToken:${appid}`;
}
